package com.htfyun.serialport.uartservo.AX12_ServoUpdate;

import android.os.Handler;
import android.os.Message;

import com.htfyun.serialport.core.SerialPortExtraControl;
import com.htfyun.serialport.utils.SerialDebugLog;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

public class AX12Update_Implementation implements OnUpdateServoListener {

    private static AX12Update_Implementation instance = null;

    private final AX12Update_Control updateControl;

    private OnUpdateListener updateListener;
    private File updateBinFile;

    private int countOfWroteDataLengthNotRised = 0;
    private final int MAX_countOfWroteDataLengthNotRised = 10;


    private int writeBlockIndex;
    private final List<ByteBuffer> blockList = new ArrayList<>();
    private final int BLOCK_MAX_SIZE = 128;//最大128字节

    private volatile boolean isUpdating = false;
    private final Object lock = new Object();

    private MyHandler handler;

    private AX12Update_Implementation() {

        updateControl = AX12Update_Control.getInstance();

        handler = new MyHandler(this);

    }

    public static AX12Update_Implementation getInstance(){
        if (instance == null) {

            synchronized (AX12Update_Implementation.class) {

                if (instance == null) {
                    instance = new AX12Update_Implementation();
                }
            }
        }
        return instance;
    }

    public void startUpdate(File bin, OnUpdateListener listener) {

        synchronized (lock) {
            if (isUpdating) {
                SerialDebugLog.e("We are updating, bin file is = " + updateBinFile);
                return;
            }
            isUpdating = true;
        }

        updateControl.registerUpdateServoListener(this);

        updateListener = listener;
        updateBinFile = bin;

        SerialDebugLog.e("We are going to update, while it will stop until transfer all bin data");

        doStartUpdate();
    }

    private void doStartUpdate() {

        if (!putBinIntoBlockList()) {

            SerialDebugLog.e("error when putBinIntoBlockList");
            updateErrorOccurred(UpdateErrorAttr.update_file_error);
            return;
        }

        SerialPortExtraControl.setServoPowerOn(false);
        handler.sendEmptyMessageDelayed(MyHandler.MSG_SERVO_POWER_UP, MyHandler.DELAY_SERVO_POWER_UP);
    }


    private void sendHandShake() {
        updateControl.sendCmdHandshakeWithBinTotalSize(getBinFileLength());
    }

    public void sendEraser() {
        updateControl.sendCmdEraser();
    }

    public void forceStopUpdate() {

        synchronized (lock) {
            isUpdating = false;
        }

        handler.removeMessages(MyHandler.MSG_SEND_HANDSHAKE);
        handler.removeMessages(MyHandler.MSG_SEND_ERASER);
        handler.removeMessages(MyHandler.MSG_SEND_REWRITE);
        handler.removeMessages(MyHandler.MSG_SERVO_POWER_UP);

        SerialPortExtraControl.setServoPowerOn(false);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        SerialPortExtraControl.setServoPowerOn(true);

    }

    private int getBinFileLength() {
        if (updateBinFile == null) {
            SerialDebugLog.e("updateBinFile is null");
            return -1;
        }

        if (!updateBinFile.exists()) {
            SerialDebugLog.e("updateBinFile is not exist. please check, updateBinFile path = " + updateBinFile.getPath());
            return -1;
        }

        SerialDebugLog.e("updateBinFile.length() = " + updateBinFile.length());


        return (int) updateBinFile.length();
    }
    private boolean putBinIntoBlockList() {

        if (updateBinFile == null) {
            SerialDebugLog.e("updateBinFile is null");
            return false;
        }

        if (!updateBinFile.exists()) {
            SerialDebugLog.e("updateBinFile is not exist. please check");
            return false;
        }

        InputStream read = null;
        blockList.clear();

        try {
            read = new FileInputStream(updateBinFile);
            byte[] readBuffer = new byte[BLOCK_MAX_SIZE];
            int length = 0;

            while ((length = read.read(readBuffer)) != -1) {

                ByteBuffer byteBuffer = ByteBuffer.allocate(length);
                if (length < readBuffer.length) {

                    SerialDebugLog.e("length = " + length);

                    byte[] finalData = new byte[length];
                    System.arraycopy(readBuffer, 0, finalData, 0, length);
                    byteBuffer.put(finalData);
                } else {
                    byteBuffer.put(readBuffer);
                }
                blockList.add(byteBuffer);

            }

        } catch (Exception e) {
            SerialDebugLog.e("Exception msg = " + e.getMessage());
            return false;

        } finally {

            if (read != null) {
                try {
                    read.close();
                    read = null;
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }

        SerialDebugLog.e("blockList.size = " + blockList.size());


        return true;
    }

    private void updateErrorOccurred(UpdateErrorAttr attr) {
        synchronized (lock) {
            isUpdating = false;
        }

        updateControl.unregisterUpdateServoListener(this);


        if (updateListener != null) {
            updateListener.onUpdateError(attr);
        }
    }

    private void updateOver() {
        synchronized (lock) {
            isUpdating = false;
        }

        updateControl.unregisterUpdateServoListener(this);

        if (updateListener != null) {
            updateListener.onUpdateListener(1.0f);
            updateListener.onUpdateError(UpdateErrorAttr.update_ok);
        }
    }

    private void updatePercent(float percent) {

        if (updateListener != null) {
            updateListener.onUpdateListener(percent);
        }
    }


    @Override
    public void onUpdateServoListener(AX12Update_instructionAttr instructionAttr, int wroteDataLength) {

        SerialDebugLog.e("instructionAttr = " + instructionAttr + ", wroteDataLength = " + wroteDataLength);

        if (instructionAttr == AX12Update_instructionAttr.none) {
            return;
        }
        switch (instructionAttr) {
            case handshake:
                handler.removeMessages(MyHandler.MSG_SEND_HANDSHAKE);

                handler.clearRetryTimes();
                handler.sendEmptyMessage(MyHandler.MSG_SEND_ERASER);
                break;
            case eraser:
                handler.removeMessages(MyHandler.MSG_SEND_ERASER);
                doSendFirstBin();
                break;
            case write:
                doSendNextBin(wroteDataLength);
                break;
        }
    }

    private void doSendFirstBin() {

        writeBlockIndex = 0;
        doSendBinDataWithIndex(writeBlockIndex);

    }

    private void doSendNextBin(int wroteDataLength) {

        writeBlockIndex += 1;

        int sentDataLen = 0;
        for (int i = 0; i < writeBlockIndex && i < blockList.size(); i++) {
            sentDataLen += blockList.get(i).capacity();
        }

        if (wroteDataLength != sentDataLen) {
            //舵机反馈的数据没有递增, 这个有可能会失败;
            countOfWroteDataLengthNotRised++;
            //继续发送当前index
            writeBlockIndex--;

            SerialDebugLog.e("safdf: sentDataLen = " + sentDataLen
                    + ", wroteDataLength = " + wroteDataLength + ", writeBlockIndex = " + writeBlockIndex);

        } else {
            countOfWroteDataLengthNotRised = 0;
        }

        if (countOfWroteDataLengthNotRised >= MAX_countOfWroteDataLengthNotRised) {
            updateErrorOccurred(UpdateErrorAttr.update_failed);
            return;
        }

        if (writeBlockIndex < 0) {
            writeBlockIndex = 0;
        }

        doSendBinDataWithIndex(writeBlockIndex);

    }

    private void doSendBinDataWithIndex(int writeBlockIndex) {

        handler.removeMessages(MyHandler.MSG_SEND_REWRITE);

        if (blockList.isEmpty()) {
            SerialDebugLog.e("What happened, blockList is empty");
            return;
        }

        if (writeBlockIndex >= blockList.size()) {
            updateOver();
            return;
        }

        int writingDataLen = 0;
        for (int i = 0; i < writeBlockIndex && i < blockList.size(); i++) {
            writingDataLen += blockList.get(i).capacity();
        }

        byte[] data = blockList.get(writeBlockIndex).array();
        writingDataLen += data.length;
        updateControl.sendBinData(writingDataLen, data);

        SerialDebugLog.e("writeBlockIndex = " + writeBlockIndex + ", writingDataLen = " + writingDataLen);

        updatePercent(1.0f * writeBlockIndex / blockList.size());

        Message msg = handler.obtainMessage(MyHandler.MSG_SEND_REWRITE, writeBlockIndex, 0);
        handler.sendMessageDelayed(msg, MyHandler.TIMEOUT_MSG_SEND);

    }

    private static final class MyHandler extends Handler {

        public static final int MSG_SERVO_POWER_UP = 0;
        public static final int DELAY_SERVO_POWER_UP = 300;

        public static final int MSG_SEND_HANDSHAKE = 1;
        public static final int MSG_SEND_ERASER = 2;
        public static final int MSG_SEND_REWRITE = 3;
        public static final int TIMEOUT_MSG_SEND = 50;//20ms


        private int retryTimes = 0;
        private static final int RETRY_MAX_TIMES = 10;

        private int writeSameBlockIndex = -1;


        private final WeakReference<AX12Update_Implementation> reference;

        public MyHandler(AX12Update_Implementation implementation) {
            reference = new WeakReference<>(implementation);
        }

        private void clearRetryTimes() {
            retryTimes = 0;
        }
        @Override
        public void handleMessage(Message msg) {
            AX12Update_Implementation implementation = reference.get();
            if (implementation == null) {
                return;
            }

            switch (msg.what) {
                case MSG_SEND_HANDSHAKE:
                    implementation.sendHandShake();
                    sendEmptyMessageDelayed(MSG_SEND_HANDSHAKE, TIMEOUT_MSG_SEND);
                    if (retryTimes++ > RETRY_MAX_TIMES) {
                        implementation.forceStopUpdate();
                        implementation.updateErrorOccurred(UpdateErrorAttr.update_failed);
                    }

                    break;
                case MSG_SEND_ERASER:
                    implementation.sendEraser();
                    sendEmptyMessageDelayed(MSG_SEND_ERASER, TIMEOUT_MSG_SEND);
                    if (retryTimes++ > RETRY_MAX_TIMES) {
                        implementation.forceStopUpdate();
                        implementation.updateErrorOccurred(UpdateErrorAttr.update_failed);
                    }

                    break;
                case MSG_SEND_REWRITE:

                    int writeBlockIndex = msg.arg1;

                    if (writeSameBlockIndex != writeBlockIndex) {
                        clearRetryTimes();
                    } else if (retryTimes++ > RETRY_MAX_TIMES) {
                        implementation.forceStopUpdate();
                        implementation.updateErrorOccurred(UpdateErrorAttr.update_failed);
                        break;
                    }

                    writeSameBlockIndex = writeBlockIndex;

                    implementation.doSendBinDataWithIndex(writeBlockIndex);

                    break;
                case MSG_SERVO_POWER_UP:
                    SerialPortExtraControl.setServoPowerOn(true);
                    sendEmptyMessage(MyHandler.MSG_SEND_HANDSHAKE);
                    clearRetryTimes();

                    break;
                    default:break;

            }

        }
    }



    /////////////////////////////////////////////////
    /////////////////////////////////////////////////
    public interface OnUpdateListener {
        /**
         *
         * @param percent 0~1.0f, 1.0f时, 表示升级完成了.
         */
        void onUpdateListener(float percent);
        void onUpdateError(UpdateErrorAttr attr);
    }

    public enum UpdateErrorAttr {
        update_ok,
        update_failed,
        update_file_error,
    }


}
